iT邦幫忙

2022 iThome 鐵人賽

DAY 6
0
Modern Web

那些年我們一起 review 的 code系列 第 6

那些年我們一起 review 的 code:分頁處理

  • 分享至 

  • xImage
  •  

提到分頁,大部分的人會很直覺想到 offset pagination,也就是第幾頁的模式,然而實際上還有另一種分頁處理的方式稱為 cursor pagination,也就是只能載入目前載入位置後面的資料,並不能跳到特定頁數載入,常用於滾動載入的模式。在我們團隊中,盡可能使用 cursor pagination 的方式來實作,而非 offset pagination。

Offset pagination

首先簡單介紹一下 offset pagination,基本上會有兩個參數:頁數、每頁筆數。透過這兩個參數就可以去資料庫裡面拿到對應的資料,例如每頁 100 筆,第三頁的話,我們就可以在資料庫中拿取第 201 - 300 筆的資料,這樣看起來不錯,會有什麼問題呢?
這邊提供一篇不錯的文章給大家參考:Is offset pagination dead? Why cursor pagination is taking over。當我們要查找的頁面越後面,例如第 9999 頁,那麼實際上資料庫並不會直接跳到第 9999 頁,而是一樣從第一頁掃過去,因此當資料越多、頁面越多的時候,整體效能就會隨之增加。

Cursor pagination

使用 cursor 的原理非常簡單,就是抓最後一筆,然後再往後拿資料。這樣想就太天真了!cursor pagination 最怕遇到的就是重複資料的問題,讓我們看看以下例子:

id,     created_at
a1,     2022-01-01+09:00
b1,     2022-01-01+09:00
c1,     2022-01-01+09:00
d1,     2022-01-01+09:00
a2,     2022-01-01+19:00
b2,     2022-01-01+19:00
c2,     2022-01-01+19:00
d2,     2022-01-01+19:00
e2,     2022-01-01+19:00

以上是我們有的所有資料,假設我們想按照 created_at 做遞增查詢,也就是先看到比較早建立的資料,然後每次看 3 筆,那麼我們的查詢會這樣下:

orderBy: created_at
limit: 3
where: created_at >= lastCreatedAt

那麼我們第一次會拿到:

a1, 2022-01-01+09:00
b1, 2022-01-01+09:00
c1, 2022-01-01+09:00

接著載入更多的話會去抓最後一筆,也就是 c1 的 created_at,並且要大於等於,於是第二次載入變成:

a1, 2022-01-01+09:00
b1, 2022-01-01+09:00
c1, 2022-01-01+09:00
a1, 2022-01-01+09:00
b1, 2022-01-01+09:00
c1, 2022-01-01+09:00

我們發現資料重複了!那麼改成只有大於呢?也不行,因為這樣 d1 會被跳掉,因此我們需要找到絕不重複的欄位進來一同排序,將查詢改成:

orderBy: created_at, id
limit: 3
where: created_at >= lastCreatedAt and id > lastId

那麼我們第一次一樣是拿到:

a1, 2022-01-01+09:00
b1, 2022-01-01+09:00
c1, 2022-01-01+09:00

接著載入更多的話會去抓最後一筆,也就是 c1 的 created_at 跟 id,因為 created_at 要大於等於且 id 也要大於,於是第二次載入變成:

a1, 2022-01-01+09:00
b1, 2022-01-01+09:00
c1, 2022-01-01+09:00
d1, 2022-01-01+09:00
a2, 2022-01-01+19:00
b2, 2022-01-01+19:00

看起來完美解決這個問題了!其實錯了,如果我們調整成一次拿 4 筆就會發現...
這是第一次的查詢:

a1, 2022-01-01+09:00
b1, 2022-01-01+09:00
c1, 2022-01-01+09:00
d1, 2022-01-01+09:00

接著載入更多的話會去抓最後一筆,也就是 d1 的 created_at 跟 id,因為 created_at 要大於等於且 id 也要大於,這邊補充一下因為此處的 id 是採用字串,所以是使用字典排序,因此 id 要比 d1 大的話,只有 d2 跟 e2 而已,因此第二次載入就會變成:

a1, 2022-01-01+09:00
b1, 2022-01-01+09:00
c1, 2022-01-01+09:00
d1, 2022-01-01+09:00
d2, 2022-01-01+19:00
e2, 2022-01-01+19:00

顯然地我們的資料不見了,因此正確的方式應該是要將重複的資料拆出來判斷,調整查詢如下:

orderBy: created_at, id
limit: 3
where: created_at > lastCreatedAt or (created_at = lastCreatedAt and id > lastId)

也就是相同資料再去比較 id,如果 created_at 已經比較大的話就不用再比較了。

回到本篇主軸,我們團隊目前使用分頁的話會預設是 cursor 的模式進行,頂多在前端做 offset pagination,至於 cursor 也會注意是否有重複值,並使用上述方式排解,希望對大家有幫助。


上一篇
那些年我們一起 review 的 code:團隊協作
下一篇
那些年我們一起 review 的 code:多國語系
系列文
那些年我們一起 review 的 code9
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言